/*
 * main.c
 *
 *  Created on: 2012-8-2
 *      Author: wx
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <time.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <dirent.h>

#include "log.h"
#include "tcpsession.h"
#include "util.h"
#include "xml/mxml.h"
#include "ini/inifile.h"

#define BUFFER_SIZE 1048576 * 64
#define DEFALT_CONFIGURATION "/etc/parsesoap.ini"
#define BUF_SIZE 1024

/*
 * 1、目标：解析出tcp整个会话的数据
 * 2、每个会话的数据包，都用链表方式依次保存
 * 3、在收到fin或rst标志时，解析整个链表数据，进行处理。然后释放链表内存
 * 4、每次处理完数据包后，检查是否是soap会话数据，如果不是，立刻删除会话，释放链表数据。
 * 5、不存在会话的包丢弃
 * 6、每个包的数据结构中要包括时间戳、地址、端口、tcp标志、序号、确认序号、mac（考虑是否需要）、IPID（考虑是否需要）、tcp payload
 * 7、每次开始一个tcp会话，创建一个数据包链表。所有链表头存放于预定义数组中，即并发处理能力。以后可以考虑进行二次扫描，处理超出的会话。
 * 8、考虑采用回调函数的方式，对会话进行过滤。在收到第一个有tcp数据的包时进行过滤，或者在每个有效tcp包时进行过滤。
 * 9、考虑cap文件中，剩余不完整的会话包，是否要在解析下一个连续的cap文件时继续保存。
 * 10、相同端口作为一个会话，知道fin并ack后，会话结束。
 * 11、ip层除了地址外，还要考虑处理分段。
 */

static char g_input_commit[BUF_SIZE] = {0};
static char g_input_work[BUF_SIZE] = {0};
static char g_output_commit[BUF_SIZE] = {0};
static char g_output_work[BUF_SIZE] = {0};

static char *g_cfg_file;
static char g_log_file[BUF_SIZE] = {0};

static char g_business_platform[BUF_SIZE] = {0};
static char g_business_bizname[BUF_SIZE] = {0};
static char g_business_netid[BUF_SIZE] = {0};

static void parsepcap(const char * filename);
static int parsedata(TCPSession_t *session);
static void usage();
static int getconfiguration();
static int checkdata(const char * buf, int len);

struct inotify_event_s {
	int			wd;
	uint32_t	mask;
	uint32_t	cookie;
	uint32_t	len;
};

int main(int argc, char * argv[])
{
	struct inotify_event_s *ie;
	char buf[BUF_SIZE];
	char filename[BUF_SIZE];
	int len, pos, count, datalen;
	int monitor, watcher;

//	printf("1336.cap=====================\n");
//	parsepcap("/home/wx/cap/1336.cap");
//	printf("1419.cap=====================\n");
//	parsepcap("/home/wx/cap/1419.cap");

	//解析参数
	if (argc == 1)
	{
		g_cfg_file = DEFALT_CONFIGURATION;
	} else if (argc == 2)
	{
		g_cfg_file = argv[1];
	} else
	{
		usage();
		return -1;
	}

	//读取配置文件
	if (getconfiguration() != 0) return -1;

	//初始化日志
	if (InitLog(g_log_file)==-1) return -1;

//	parsepcap(argv[1]);

	//初始化协议解析库
	if (tcpsess_init()<0)
	{
		SendLog("Failed to init tcp session parser\n");
		exit(0);
	}

	//开始监测目录
	if ((monitor = inotify_init())==-1)
	{
		SendLog("monitor error: %s", g_input_commit);
		return -1;
	}

	if ((watcher = inotify_add_watch(monitor, g_input_commit, IN_ALL_EVENTS)) == -1)
	{
		SendLog("inotify_add_watch error");
		return -1;
	}

	//处理现有的文件
	DIR *dp;
	struct dirent *ep;
	if ((dp = opendir(g_input_commit))==NULL)
	{
		SendLog("Failed to open input commit directory.");
		return -1;
	}
	while ((ep = readdir(dp)))
	{
		if (ep->d_type == DT_REG)
		{
			//删除文件
			if (g_input_commit[strlen(g_input_commit)-1]=='/')
				sprintf(filename, "%s%s", g_input_commit, ep->d_name);
			else
				sprintf(filename, "%s/%s", g_input_commit, ep->d_name);
			unlink(filename);

			//解析文件
			if (g_input_work[strlen(g_input_work)-1]=='/')
				sprintf(filename, "%s/%s", g_input_work, ep->d_name);
			else
				sprintf(filename, "%s/%s", g_input_work, ep->d_name);
			parsepcap(filename);
		}
	}
	closedir(dp);


	len = 0;
	datalen = 0;
	while(1)
	{
		pos = 0;
		count = read(monitor, buf + len, BUF_SIZE - len);
		if (count<=0) {
			SendLog("monitor read error");
			break;
		}
		len += count;
		while(1)
		{
			if (datalen == 0)
			{
				if (len - pos >= sizeof(*ie))
				{
					ie = (struct inotify_event_s *)(buf+pos);
//SendLog("mask=%u\tlen=%u\twd=%d\tcookie=%u\n", ie->mask, ie->len, ie->wd, ie->cookie);
					pos += sizeof(*ie);
					datalen = ie->len;
				} else break;
			}
			if (datalen > 0)
			{
				if (len - pos >= datalen)
				{
					if (ie->mask == IN_CLOSE_WRITE)
					{
						//删除文件
						if (g_input_commit[strlen(g_input_commit)-1]=='/')
							sprintf(filename, "%s%s", g_input_commit, buf+pos);
						else
							sprintf(filename, "%s/%s", g_input_commit, buf+pos);
						unlink(filename);

						//解析文件
						if (g_input_work[strlen(g_input_work)-1]=='/')
							sprintf(filename, "%s/%s", g_input_work, buf+pos);
						else
							sprintf(filename, "%s/%s", g_input_work, buf+pos);
						parsepcap(filename);
					}
					pos += ie->len;
					datalen = 0;
				} else break;
			}
		}
		if (pos!=len)
		{
			SendLog("monitor data error");
			memcpy(buf, buf+pos, len-pos);
		}
		len = len - pos;
	}

	//关闭协议解析库
	tcpsess_close();

	return 0;
}

static void parsepcap(const char * filename)
{
	static int i,j;
	static int iSessionCount = 0;
	static TCPSession_t **sessions;


	if (tcpsess_parse_pcap(filename)<0)
	{
		SendLog("Parse Error\n");
		exit(0);
	}

	if ((iSessionCount = tcpsess_get_sess_count())==0)
	{
		SendLog("No TCP session\n");
		exit(0);
	}

	sessions = tcpsess_get_sessions();

	for (i=j=0;i<iSessionCount;i++,j++)
	{
		if (parsedata(*(sessions+j)) != 1)
		{
			//删除处理完成的session
			//删除无效的session
			tcpsess_del_session(j);
			j--;
		}

//		if (session->Flags == TCPFLAG_SYN)
//		{
//			//printf("src=%u:%d\tdst=%u:%d\n", session->SrcIP, session->SrcPort, session->DstIP, session->DstPort);
//			printf("========================================\n");
/*			while (session)
			{
				printf("%d\t", session->PkgNo);
				printf("%s\t", GetTimeStr(session->ts));
				printf("src=%u:%d, dst=%u:%d\t", session->SrcIP, session->SrcPort, session->DstIP, session->DstPort);
				printf("%d\t", session->TotalLen);
				printf("%d\t", session->DataLen);
				printf("seq=%u\tack=%u\t", session->SeqNo, session->AckNo);
				if (session->Flags & TCPFLAG_SYN) printf("SYN ");
				if (session->Flags & TCPFLAG_ACK) printf("ACK ");
				if (session->Flags & TCPFLAG_PSH) printf("PSH ");
				if (session->Flags & TCPFLAG_FIN) printf("FIN ");
				if (session->Flags & TCPFLAG_RST) printf("RST ");
				if (session->Flags & TCPFLAG_UTG) printf("UTG ");

				printf("\n");
				session = session->next;
			}
*/
//		}

	}

}

//成功处理完成返回0
//无效会话返回-1
//应用层数据未完成返回1
static int parsedata(TCPSession_t *session)
{
	//根据应用层数据检查完整性
	static char buf[BUFFER_SIZE];
	static char *req, *rsp, *pos;
	static TCPSession_t * s;
	static int datalen;
	static int len;
	static int err;

	if (!session) return -1;

	//拼接数据

//printf("sess data begin============================\n");
//	if (tcpsess_get_req_data(session, buf, BUFFER_SIZE)<0)
//	{
//		return -1;
//	}
//printf("%s\n", buf);
//	if (tcpsess_get_rsp_data(session, buf, BUFFER_SIZE)<0)
//	{
//		return -1;
//	}
//printf("%s\n",buf);
//printf("sess data end============================\n");
	if ((datalen = tcpsess_get_sess_data(session, buf, BUFFER_SIZE))<0)
	{
		SendLog("Failed to get session data.");
		return -1;
	}
//	s = session;
//	bzero(buf, BUFFER_SIZE);
//	while (s)
//	{
//		if (s->DataLen>0)
//		{
//			strcat(buf, s->Payload);
//		}
//		s = s->next;
//	}
//printf("%s\n", buf);

	//检查数据
	if ((err = checkdata(buf, datalen)) != 0)
	{
//printf("not precontract msisdn\n");
		SendLog("tcp data error.");
		return err;
	}

printf("--------------------------\n");
	//分析请求数据
	if ((pos = strstr(buf, "\r\n\r\n"))==NULL) return -1;
	pos += 4;
	req = pos;
	if ((pos = strstr(pos, "\r\n"))==NULL) return -1;
	*pos = 0;
	pos += 2;
	len = strtol(req, NULL, 16);
	req = pos;
	pos += len;
	*pos = 0;
//printf("%s\n", req);

	pos++;
	if ((pos = strstr(pos, "\r\n\r\n"))==NULL) return -1;
	pos += 4;

	//分析响应数据
	if ((pos = strstr(pos, "\r\n\r\n"))==NULL) return -1;
	pos += 4;
	rsp = pos;
	if ((pos = strstr(pos, "\r\n"))==NULL) return -1;
	*pos = 0;
	pos += 2;
	len = strtol(rsp, NULL, 16);
	rsp = pos;
	pos += len;
	*pos = 0;
//printf("%s\n", rsp);

	//解析关键字
	static mxml_node_t *nReq, *nRsp, *node;
	if ((nReq = mxmlLoadString(NULL, req, NULL))==NULL)
	{
		SendLog("request data is not xml format.");
		return -1;
	}
	node = mxmlFindPath(nReq, "*/ns79:_msisdn");
	if (node) printf("预占号码：%s\n", node->value.text.string);
	node = mxmlFindPath(nReq, "*/ns79:_reg_nbr");
	if (node) printf("用户身份ID：%s\n", node->value.text.string);
	mxmlDelete(nReq);

	if ((nRsp = mxmlLoadString(NULL, rsp, NULL))==NULL)
	{
		SendLog("response data is not xml format.");
		return -1;
	}
	node = mxmlFindPath(nRsp, "*/n:ErrorCode");
	if (node) printf("服务器返回码：%s\n", node->value.text.string);
	node = mxmlFindPath(nRsp, "*/n:ErrorMsg");
	if (node) printf("服务器返回信息：%s\n", node->value.text.string);
	mxmlDelete(nRsp);

	//三次握手时间
	static int syntime;
	s = session;
	syntime = 0;
	if ((s->Flags == TCPFLAG_SYN)
		&&(s->next->Flags == (TCPFLAG_SYN | TCPFLAG_ACK))
		&&(s->next->next->Flags == TCPFLAG_ACK))
	{
		syntime = (s->next->next->ts.tv_sec - s->ts.tv_sec) * 1000000 + s->next->next->ts.tv_usec - s->ts.tv_usec;
	}
	printf("三次握手时间：%d\n", syntime);

	//请求开始时间
	s = session;
	if (s->Flags == TCPFLAG_SYN)
		printf("请求开始时间：%s\n", GetTimeStr(s->ts.tv_sec, s->ts.tv_usec));
	else
		printf("请求开始时间：0\n");

	//请求结束时间
	s = session;
	int i;
	while(s)
	{
		i++;
		if (s->next == NULL)
		{
			printf("请求结束时间：0\n");
			break;
		}
		if ((s->Flags == (TCPFLAG_ACK | TCPFLAG_FIN))
			&&((s->next->Flags == TCPFLAG_ACK)||(s->next->Flags == (TCPFLAG_ACK | TCPFLAG_RST))))
		{
			printf("请求结束时间：%s\n", GetTimeStr(s->next->ts.tv_sec, s->next->ts.tv_usec));
			break;
		}
		s = s->next;
	}
//printf("%d\n",i);

	//todo 写入输出文件

	return 0;
}

static void usage()
{
	printf("Usage: parsesoap [configuration file]\n");
	printf("The default configuration file is /etc/parsesoap.ini\n");
}

static int getconfiguration()
{
	if(!read_profile_string("log", "logfile", g_log_file, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}
	if(!read_profile_string("input", "commit", g_input_commit, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}
	if(!read_profile_string("input", "work", g_input_work, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}
	if(!read_profile_string("output", "commit", g_output_commit, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}
	if(!read_profile_string("output", "work", g_output_work, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}
	if(!read_profile_string("business", "platform", g_business_platform, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}
	if(!read_profile_string("business", "bizname", g_business_bizname, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}
	if(!read_profile_string("business", "netid", g_business_netid, BUF_SIZE, "" , g_cfg_file))
	{
		printf("Failed to read configuration file\n");
		return -1;
	}

	return 0;
}

//检查完整soap请求和响应数据
//完整soap数据返回0
//没有数据或者只有前半部分，返回1
//其他数据或者只有后部分数据，返回-1
#define SOAP_REQ_CMD "POST /aiws/services/newaccept_appService"
#define SOAP_ACTION "SOAPAction: \"/MUfiResOperMgntApp/IUfiResOperMgntApp/do_precontractMsisdn\""
#define SOAP_RSP_CMD "HTTP/1.1 200 OK"
static int checkdata(const char * buf, int len)
{
	//todo
	//if (!strstr(buf, "SOAPAction: \"/MUfiResOperMgntApp/IUfiResOperMgntApp/do_precontractMsisdn\""))

	if (len==0) return 1;

	//检查http协议的请求和响应
	if (strncmp(buf, SOAP_REQ_CMD, sizeof(SOAP_REQ_CMD)) != 0)
	{
		return -1;
	}

	if (strstr(buf, "\r\n\r\n") == NULL)
	{
		return 1;
	}

	if (strstr(buf, SOAP_ACTION) == NULL)
	{
		return -1;
	}




	return 0;
}
